package sim16;


import java.io.*;
import java.util.Scanner;

/**
 * The main simulator class invoked from the command line.
 * @author Piotr
 * 
 */
public class Sim {

	static Pipeline pipe;
	
	static final int maxCycleCount = 10000;
	static int numberOfPipes = 1;
	
	/**
	 * Main method. Constructs all necessary objects and controls execution.
	 * @param args	Filename of program to be run in the simulator. Optional filename
	 * 				of data memory initialization file.
	 */
	public static void main(String[] args) throws IOException, InvalidInstructionException {
		
		
		File programFile;
		File dataFile = null;
		
		if (args.length > 0) {
			if (args.length == 1) {
				programFile = new File(args[0]);
				if (!programFile.exists()) {
					System.out.println("File \"" + args[0] + "\" does not exist");
					System.exit(1);
				}
			} else {
				boolean pipesSpecified = false;
				try {
					numberOfPipes = Integer.parseInt(args[0]);
					if (numberOfPipes < 1 || numberOfPipes > 16) {
						System.out.println("Invalid issue width specified. Valid range is 1-16.");
						System.exit(1);
					}
					pipesSpecified = true;
					programFile = new File(args[1]);
					if (!programFile.exists()) {
						System.out.println("File \"" + args[1] + "\" does not exist");
						System.exit(1);
					}
				} catch (NumberFormatException e) { 
					numberOfPipes = 1;
					pipesSpecified = false;
					programFile = new File(args[0]);
					if (!programFile.exists()) {
						System.out.println("File \"" + args[0] + "\" does not exist");
						System.exit(1);
					}
				}
				
				if (args.length > 2 && pipesSpecified) {
					dataFile = new File(args[2]);
					if (!dataFile.exists()) {
						System.out.println("File \"" + args[2] + "\" does not exist");
						System.exit(1);
					}
				} else if (args.length > 1 && !pipesSpecified) {
					dataFile = new File(args[1]);
					if (!dataFile.exists()) {
						System.out.println("File \"" + args[1] + "\" does not exist");
						System.exit(1);
					}
				}
			}
			// Create the pipeline objects
			
			if (dataFile != null) {
				pipe = new Pipeline(numberOfPipes, programFile, dataFile);
			} else {
				pipe = new Pipeline(numberOfPipes, programFile);
			}
			int instrsInPipe = 0;
			int cycleCount = 0;
			int instrCount = 0;
			boolean programDone = false;
			
			//Main program loop
			while(!programDone && cycleCount < maxCycleCount) {
				instrsInPipe = 0;
				cycleCount++;
				instrsInPipe = pipe.cycle();
				for (int ii = 0; ii < numberOfPipes; ii++) {
					Instruction wbInstr = pipe.wbs[ii].getInstruction();
					if (wbInstr != null) {
						String tag = wbInstr.textString;
						printOutput(tag, cycleCount, instrsInPipe, ii);
					}
				}
				//if (cycleCount >= 235 && cycleCount <= 245) {
				//	printFullPipeState(cycleCount, instrsInPipe);
				//}
				instrCount += pipe.numWBInstrs();
				if (pipe.ifs.isProgramFinished() && instrsInPipe == 0) {
					programDone = true;
					System.out.println("###################### Program Completed ######################");
					System.out.println("Statistics:");
					System.out.println("Total instructions executed: " + instrCount);
					System.out.println("Total clock cycles: " + (cycleCount - 1));
					System.out.println("Average CPI: " + (float)(cycleCount - 1)/instrCount);
					System.out.println("Total bytes fetched from IM: " + pipe.ifs.getByteCount());
					System.out.println(pipe.ids.dumpRegisters());
					System.out.println(pipe.dumpMemory());
				}
				
			}
			if (cycleCount >= maxCycleCount) {
				System.out.println("Max cycle limit (" + maxCycleCount + ") reached.");
			}

		} else {
			System.out.println("No input file specified");
			System.exit(1);
		}
				

	}
	
	/**
	 * Method used to print out the pipeline state. Based on the tags appended to the
	 * instructions in out intermediate files.
	 * @param tag
	 * @param cycleCount
	 * @param instrsInPipe
	 * @param pipeID
	 */
	static private void printOutput(String tag, int cycleCount, int instrsInPipe, int pipeID) {
		//System.out.println(tag);
		Scanner sc = new Scanner(tag);
		String match = sc.findInLine("^![ipmr]+");
		if (match != null) {
			//System.out.println(match);
			sc = new Scanner(match);
			System.out.println("####################### Cycle " + cycleCount + "\t#######################");
			if (match.contains("p")) {
				for (int ii = 0; ii < numberOfPipes; ii++) {
					System.out.println("*************** IF" + ii + " Stage Instruction: ***************");
					System.out.println(pipe.ifs.getInstruction(ii));
				}
				System.out.println("==ID Stats: InsCnt: " + pipe.ids.instrCount + " ReadyCnt: "
						+ pipe.ids.readyCount + " FetchCnt: " + pipe.ids.fetchCnt 
						+ " NextFetch: " + pipe.ids.nextFetchCnt + " ==");
				for (int ii = 0; ii < numberOfPipes*2; ii++) {
					System.out.println("*************** ID" + ii + " Stage Instruction: ***************");
					System.out.println(pipe.ids.getInstruction(ii));
				}
				for (int ii = 0; ii < numberOfPipes; ii++) {
					System.out.println("*************** EX" + ii + " Stage Instruction: ***************");
					System.out.println(pipe.exs[ii].getInstruction());
				}
				for (int ii = 0; ii < numberOfPipes; ii++) {
					System.out.println("*************** MEM" + ii + " Stage Instruction: **************");
					System.out.println(pipe.mems[ii].getInstruction());
				}
				for (int ii = 0; ii < numberOfPipes; ii++) {
					System.out.println("*************** WB" + ii + " Stage Instruction: ***************");
					System.out.println(pipe.wbs[ii].getInstruction());
				}
				System.out.println("Instructions in pipeline: " + instrsInPipe);
			} else if (match.contains("i")) {
				System.out.println("*************** WB" + pipeID + " Stage Instruction: ***************");
				System.out.println(pipe.wbs[pipeID].getInstruction());
			}
			if (match.contains("r")) {
				System.out.println(pipe.ids.dumpRegisters());
			}
			if (match.contains("m")) {
				System.out.println(pipe.dumpMemory());
			}
		}
	}
	
	/**
	 * Method used to print out the pipeline state. Used for simulator debugging.
	 * @param cycleCount
	 * @param instrsInPipe
	 */
	static private void printFullPipeState(int cycleCount, int instrsInPipe) {
		System.out.println("####################### Cycle " + cycleCount + "\t#######################");
		for (int ii = 0; ii < numberOfPipes; ii++) {
			System.out.println("*************** IF" + ii + " Stage Instruction: ***************");
			System.out.println(pipe.ifs.getInstruction(ii));
		}
		System.out.println("==ID Stats: InsCnt: " + pipe.ids.instrCount + " ReadyCnt: "
				+ pipe.ids.readyCount + " FetchCnt: " + pipe.ids.fetchCnt
				+ " NextFetch: " + pipe.ids.nextFetchCnt + " ==");
		for (int ii = 0; ii < numberOfPipes*2; ii++) {
			System.out.println("*************** ID" + ii + " Stage Instruction: ***************");
			System.out.println(pipe.ids.getInstruction(ii));
		}
		for (int ii = 0; ii < numberOfPipes; ii++) {
			System.out.println("*************** EX" + ii + " Stage Instruction: ***************");
			System.out.println(pipe.exs[ii].getInstruction());
		}
		for (int ii = 0; ii < numberOfPipes; ii++) {
			System.out.println("*************** MEM" + ii + " Stage Instruction: **************");
			System.out.println(pipe.mems[ii].getInstruction());
		}
		for (int ii = 0; ii < numberOfPipes; ii++) {
			System.out.println("*************** WB" + ii + " Stage Instruction: ***************");
			System.out.println(pipe.wbs[ii].getInstruction());
		}
		System.out.println("Instructions in pipeline: " + instrsInPipe);
	}

}
